package cn.maihe.elg.operation.centers.follow.channel;

import cn.maihe.elg.operation.centers.follow.auth.AuthHelper;
import cn.maihe.elg.operation.centers.follow.channel.dto.FollowBaseRespDTO;
import cn.maihe.elg.operation.centers.follow.channel.dto.FollowRespDTO;
import cn.maihe.elg.operation.centers.follow.config.FollowConfig;
import cn.maihe.elg.operation.centers.follow.enums.MethodEnum;
import cn.maihe.elg.operation.exception.CenterException;
import cn.maihe.elg.operation.model.dto.BaseReq;
import cn.maihe.elg.operation.model.enums.InnerRespCode;
import cn.maihe.elg.operation.utils.ElgLogger;
import cn.maihe.elg.operation.utils.IdGenerate;
import cn.maihe.elg.operation.utils.MD5FileUtil;
import cn.maihe.elg.operation.utils.ValidateUtil;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.lang.reflect.ParameterizedType;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description
 * @Author WJH
 * @Date 2021/04/19
 */
@Service
@Slf4j
public abstract class FollowBaseCenterChannelService<REQ extends BaseReq, RESP extends FollowBaseRespDTO> {
    private final ObjectMapper objectMapper = new ObjectMapper();
    protected final Class<RESP> respClass;

    @Resource
    private FollowConfig followConfig;
    @Resource
    private RestTemplate restTemplate;


    public FollowBaseCenterChannelService() {
        respClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 设置公共字段：method&timestamp
     */
    protected REQ buildReq(REQ req) {
        return req;
    }

    /**
     * @param req 参与签名验签数据
     * @return
     */
    public RESP doRequest(REQ req) {
        FollowContext followContext = new FollowContext(req, null);
        try {
            //接口公共参数设置
            req = buildReq(req);

            //请求参数校验
            verifyParam(followContext);
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE_PARAM, e, "参数错误：");
            ElgLogger.error(log, l -> l.error("traceId={} => [{}]服务 参数校验失败：{}", followContext.getTraceId(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }

        //组装请求报文||请求
        RESP resp = null;
        try {
            resp = (RESP) this.doPost(followContext);
            followContext.setResp(resp);
        } catch (Exception e) {
            CenterException ce = buildCException(InnerRespCode.FAILURE, e, "请求响应异常:");
            ElgLogger.error(log, l -> l.error("traceId={} => [{}]服务 请求响应异常：{}", followContext.getTraceId(), methodEnum().getDesc(), ce.getMessage()));
            throw ce;
        }
        return resp;
    }

    protected CenterException buildCException(InnerRespCode innerRespCode, Exception exception, String message) {
        CenterException ce;
        if (exception instanceof SocketTimeoutException) {
            ce = CenterException.timeout("超时:" + message.concat(exception.getMessage()), exception);
        } else if (exception instanceof CenterException) {
            ce = (CenterException) exception;
        } else {
            if (exception != null) {
                ce = CenterException.exception("异常:" + message.concat(exception.getMessage()), exception);
            } else {
                ce = CenterException.of(innerRespCode, message, exception);
            }
        }
        return ce;
    }

    private void verifyParam(FollowContext<REQ, RESP> context) {
        ElgLogger.debug(log, l -> l.debug("traceId={} => [{}]服务 请求参数：{}", context.getTraceId(), methodEnum().getDesc(), toJsonString(context.getReq())));
        try {
            ValidateUtil.validate(context.getReq());
        } catch (Exception e) {
            throw CenterException.of(InnerRespCode.FAILURE_PARAM, "请求参数错误", e);
        }
    }

    /**
     * 渠道方法名
     *
     * @return
     */
    protected abstract MethodEnum methodEnum();

    protected abstract String getUrl(FollowConfig followConfig);

    private RESP doPost(FollowContext<REQ, RESP> context) {
        REQ req = context.getReq();
        ElgLogger.info(log, l -> l.info("traceId={} => [{}]服务 请求报文：{}", context.getTraceId(), methodEnum().getDesc(), toJsonString(req)));

        //签名
//        JSONObject param = (JSONObject) JSON.toJSON(req);
        String data = toJsonString(req);
        String dataMd5 = MD5FileUtil.getMD5String(data);
        ElgLogger.info(log, l -> l.info("data={}  md5：{}", data, dataMd5));

        Map<String, String> map = new HashMap();
        map.put("appid", followConfig.getAppId());
        map.put("requestid", IdGenerate.genContractNo());
        map.put("timestamp", AuthHelper.getTimestamp());
        map.put("version", followConfig.getVersion());
        map.put("data", dataMd5);
        map.put("appsecret", followConfig.getAppSecret());
        String sortString = AuthHelper.getSortString(map);
        String sign = AuthHelper.SHA1(sortString);
        map.remove("appsecret");
        map.put("sign", sign);
//        map.put("data",req);

        FollowRespDTO followRespDTO = FollowRespDTO.builder()
                .appid(followConfig.getAppId())
                .requestid(map.get("requestid"))
                .timestamp(map.get("timestamp"))
                .version(followConfig.getVersion())
                .sign(sign)
                .data(req)
                .build();
        ElgLogger.info(log, l -> l.info("traceId={} => [{}]服务 请求url：{}", context.getTraceId(), methodEnum().getDesc(), getUrl(followConfig)));
        ElgLogger.info(log, l -> l.info("traceId={} => [{}]服务 请求报文：{}", context.getTraceId(), methodEnum().getDesc(), toJsonString(followRespDTO)));
        ResponseEntity<String> response = restTemplate.postForEntity(getUrl(followConfig), followRespDTO, String.class);
        RESP resp = JSONObject.parseObject(response.getBody(), this.respClass);
        ElgLogger.info(log, l -> l.info("traceId={} <= [{}]服务 响应报文：{}", context.getTraceId(), methodEnum().getDesc(), toJsonString(resp)));

        if (response.getStatusCode() == HttpStatus.OK) {
            return resp;
        }
        throw CenterException.error("响应状态非成功：" + response.getBody(), null);
    }

    private String toJsonString(Object body) {
        try {
//            objectMapper.setSerializationInclusion(Inclusion.NON_NULL);
            return objectMapper.writeValueAsString(body);
        } catch (JsonProcessingException e) {
            throw CenterException.exception("转换json字符串异常", e);
        }
    }


}
